﻿using MongoDB.Driver;
using SimpleInjector;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.Entities;
using Sustainalytics.Entities.Governance;
using Sustainalytics.Entities.ConnectionStrings;
using Sustainalytics.ESG.Entities;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Synchronization.Interface;
using Sustainalytics.Synchronization.Validation.Handlers.Common;
using Sustainalytics.Synchronization.Validation.Internals;
using Sustainalytics.Synchronization.Validation.Log;
using Sustainalytics.Synchronization.Validation.ValidationRules.Common;
using Sustainalytics.Synchronization.Validation.ValidationRules.Comprehensive;
using Sustainalytics.Synchronization.Validation.ValidationRules.Core;
using Sustainalytics.Synchronization.Validation.ValidationRules.CorporateGovernance;
using Sustainalytics.Synchronization.Validation.ValidationRules.Events;
using Sustainalytics.Synchronization.Validation.Validators;
using System.Collections.Generic;
using System.Configuration.Abstractions;
using System.Linq;
using System.Net.Mail;
using System.Threading.Tasks;

namespace Sustainalytics.Synchronization.Validation
{
    public static class ValidationCompositionRoot
    {
        public static async Task<Container> RegisterAsync(
            string validatorsConnectionString,
            SyncContext context)
        {
            var container = new Container();
            container.RegisterCollections(context.LastGAConnectionStrings, context.GAConnectionStrings);
            container.RegisterDictionaries(context.GAConnectionStrings);

            await container.RegisterValidationRulesAsync(validatorsConnectionString).ConfigureAwait(false);
            container.RegisterValidators();
            container.RegisterDataHandlers();
            container.RegisterLoggers(context);
            container.Register<ValidationEngine>();
            container.Verify();
            return container;
        }

        private static void RegisterDictionaries(
            this Container container, IDictionary<string, string> connectionStrings)
        {
            container.Register<IndicatorsHashSet>();

            // todo: refactor
            container.Register<IHashSetLoader>(
                () =>
                new IndicatorsTemplateHashSetLoader(
                    MongoFactory.GetCollectionFromDatabase<GovernanceIndicatorTemplate>(
                        connectionStrings[ConnectionStringType.GovernanceDW.ToString()]),
                        container.GetInstance<IndicatorsHashSet>()));

            container.RegisterInitializer<IHashSetDependent<string>>(
              ruleToInitialize =>
              {
                  ruleToInitialize.SetDependency(container.GetInstance<IndicatorsHashSet>());
              });

            container.Register<WhiteListCompanies>(Lifestyle.Singleton);
            container.Register<WhiteListCompaniesLoader>();
            container.Register<ComprehensiveCompanies>(Lifestyle.Singleton);
            container.Register<CoreCompanies>(Lifestyle.Singleton);
            container.Register<CorporateGovernanceCompanies>(Lifestyle.Singleton);
            container.Register<ControversyCompanies>(Lifestyle.Singleton);
            container.Register<Handlers.Comprehensive.ComprehensiveCorporateDataLoader>();
            container.Register<Handlers.Core.CoreCorporateDataLoader>();
            container.Register<Handlers.CorporateGovernance.GovernanceCorporateDataLoader>();
            container.Register<Handlers.Events.ControversyCorporateDataLoader>();
            container.Register<RulesList>(Lifestyle.Singleton);
        }

        private static void RegisterLoggers(this Container container, SyncContext context)
        {
            container.Register(() => ConfigurationManager.Instance.AppSettings, Lifestyle.Singleton);
            container.Register(
                () =>
                new SmtpClient(
                     container.GetInstance<IAppSettings>().Get("SMTPServer"),
                     int.Parse(container.GetInstance<IAppSettings>().Get("SMTPServerPort")))
                {
                    Credentials =
                                         new System.Net.NetworkCredential(
                                             container.GetInstance<IAppSettings>().Get("SMTPUserName"),
                                             container.GetInstance<IAppSettings>().Get("SMTPPassword")),
                    EnableSsl = true
                },
                Lifestyle.Singleton);
            container.Register(
                () => new MailClient(
                          container.GetInstance<EmailSettings>(),
                          container.GetInstance<SmtpClient>(),
                          container.GetInstance<IAppSettings>().Get("DeploymentLocation"),
                          container.GetInstance<MessageFormatter>(),
                          container.GetInstance<RulesList>()));

            container.Register(
                () =>
                new InternalMailClient(
                    container.GetInstance<EmailSettings>(),
                    container.GetInstance<SmtpClient>(),
                    container.GetInstance<IAppSettings>().Get("DeploymentLocation"),
                    container.GetInstance<InternalMessageFormatter>(),
                    container.GetInstance<RulesList>()));


            container.Register(() => new LogValidationResult(
               container.GetInstance<IMongoCollection<ValidationLog>>(),
               container.GetInstance<MessageFormatter>()));

            container.Register(() => new SyncSummaryLogClient(context.Log));

            var loggingAssemblies = new[] { typeof(ILogClient).Assembly };
            container.RegisterCollection<ILogClient>(loggingAssemblies);
            container.Register(() => new Log.Log(container.GetAllInstances<ILogClient>()));

            container.RegisterDecorator<IHandler, SyncLogHandlerDecorator>(Lifestyle.Singleton);
            container.RegisterInitializer<SyncLogHandlerDecorator>(logger => { logger.SetLoggingOutput(context.Log); });
        }

        private static void RegisterCollections(this Container container, Dictionary<string, string> currentConnectionStrings, Dictionary<string, string> syncConnectionStrings)
        {
            container.RegisterSingleton(typeof(IMongoCollectionFactory<>), typeof(MongoCollectionFactory<>));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<RPControversyIndicator>(syncConnectionStrings, currentConnectionStrings, x => x["ClientDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<RPIndicatorExtended>(syncConnectionStrings, currentConnectionStrings, x => x["ClientDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<RPCompanyProfile>(syncConnectionStrings, currentConnectionStrings, x => x["ClientDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<CompanyWeights>(syncConnectionStrings, currentConnectionStrings, x => x["ClientDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<EsgResearchProfile>(syncConnectionStrings, currentConnectionStrings, x => x["ClientDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<CorporateData>(syncConnectionStrings, currentConnectionStrings, x => x["ClientDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<RPCompanyESGComputationResult>(syncConnectionStrings, currentConnectionStrings, x => x["ClientDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<CompanyKeyESGIssue>(syncConnectionStrings, currentConnectionStrings, x => x["ClientDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<Source>(syncConnectionStrings, currentConnectionStrings, x => x["ClientDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<EventIndicator>(syncConnectionStrings, currentConnectionStrings, x => x["ClientDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<GovernanceIssue>(syncConnectionStrings, currentConnectionStrings, x => x["GovernanceDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<GovernanceIndicator>(syncConnectionStrings, currentConnectionStrings, x => x["GovernanceDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<GovernanceComputationResult>(syncConnectionStrings, currentConnectionStrings, x => x["GovernanceDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<GovernanceSource>(syncConnectionStrings, currentConnectionStrings, x => x["GovernanceDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<GovernanceCompanyProfile>(syncConnectionStrings, currentConnectionStrings, x => x["GovernanceDW"]));
            container.Register(() => ValidationMongoCollectionFactory.GetCollectionFromDatabase<SharedEntities.CorporateGovernanceResearch.CorporateGovernanceResearchProfile>(syncConnectionStrings, currentConnectionStrings, x => x["GovernanceDW"]));

            container.Register(() => MongoFactory.GetCollectionFromDatabase<SharedEntities.CorporateGovernanceResearch.CorporateGovernanceResearchProfile>(syncConnectionStrings["GovernanceDW"]));
            container.Register(() => MongoFactory.GetCollectionFromDatabase<GovernanceCompanyProfile>(syncConnectionStrings["GovernanceDW"]));
            container.Register(() => MongoFactory.GetCollectionFromDatabase<CorporateData>(syncConnectionStrings["ClientDW"]));
            container.Register(() => MongoFactory.GetCollectionFromDatabase<CompanyProfileResearchState>(syncConnectionStrings["ClientDW"]));
        }

        private static void RegisterDataHandlers(this Container container)
        {
            var handlerWithGroupingAssemblies = new[] { typeof(HandlerWithGrouping<>).Assembly };
            var handlerTypesToRegister = container.GetTypesToRegister(typeof(HandlerWithGrouping<>), handlerWithGroupingAssemblies);
            container.Register(typeof(HandlerWithGrouping<>), handlerTypesToRegister);

            var handlerAssemblies = new[] { typeof(IHandler).Assembly };
            var typesToRegister = container.GetTypesToRegister(typeof(IHandler), handlerAssemblies).Where(x => x != typeof(SyncLogHandlerDecorator));
            container.RegisterCollection(typeof(IHandler), typesToRegister);
        }

        private static void RegisterValidators(this Container container)
        {
            var validatorAssemblies = new[] { typeof(Validator<>).Assembly };

            var coreValidatorTypes = container.GetTypesToRegister(typeof(CoreValidator<>), validatorAssemblies, new TypesToRegisterOptions { IncludeGenericTypeDefinitions = true });
            container.RegisterCollection(typeof(CoreValidator<>), coreValidatorTypes);

            var comprehensiveValidatorTypes = container.GetTypesToRegister(typeof(ComprehensiveValidator<>), validatorAssemblies, new TypesToRegisterOptions { IncludeGenericTypeDefinitions = true });
            container.RegisterCollection(typeof(ComprehensiveValidator<>), comprehensiveValidatorTypes);

            var eventsValidatorTypes = container.GetTypesToRegister(typeof(EventsValidator<>), validatorAssemblies, new TypesToRegisterOptions { IncludeGenericTypeDefinitions = true });
            container.RegisterCollection(typeof(EventsValidator<>), eventsValidatorTypes);

            var otherTypes = container.GetTypesToRegister(typeof(Validator<>), validatorAssemblies, new TypesToRegisterOptions { IncludeGenericTypeDefinitions = true }).Except(coreValidatorTypes).Except(comprehensiveValidatorTypes);
            container.RegisterCollection(typeof(Validator<>), otherTypes);
        }

        private static async Task RegisterValidationRulesAsync(this Container container, string connectionString)
        {
            var validationRulesLoader = new ValidationRulesLoader(
                    MongoFactory.GetCollectionFromDatabase<RuleBase>(connectionString, "Validators"),
                    MongoFactory.GetCollectionFromDatabase<RuleBase>(connectionString, "InactiveValidators"));
            var inactiveRuleNames = await validationRulesLoader.GetInactiveRuleNames().ConfigureAwait(false);

            var ruleAssemblies = new[] { typeof(Rule<>).Assembly };

            var allRulesType = container.GetTypesToRegister(typeof(RuleBase), ruleAssemblies);

            var coreRuleTypes = allRulesType.Where(t => t.IsSubclassOfRawGeneric(typeof(CoreRule<>)));
            container.RegisterCollection(typeof(CoreRule<>), coreRuleTypes.Where(x => !inactiveRuleNames.Contains(x.FullName)));

            var comprehensiveRuleTypes = allRulesType.Where(t => t.IsSubclassOfRawGeneric(typeof(ComprehensiveRule<>)));
            container.RegisterCollection(typeof(ComprehensiveRule<>), comprehensiveRuleTypes.Where(x => !inactiveRuleNames.Contains(x.FullName)));

            var eventsRuleTypes = allRulesType.Where(t => t.IsSubclassOfRawGeneric(typeof(EventsRule<>)));
            container.RegisterCollection(typeof(EventsRule<>), eventsRuleTypes.Where(x => !inactiveRuleNames.Contains(x.FullName)));

            var otherRuleTypes = container.GetTypesToRegister(typeof(Rule<>), ruleAssemblies)
                .Except(coreRuleTypes).Except(comprehensiveRuleTypes);
            container.RegisterCollection(typeof(Rule<>), otherRuleTypes.Where(x => !inactiveRuleNames.Contains(x.FullName)));

            container.Register(() => MongoFactory.GetCollectionFromDatabase<ValidationLog>(connectionString));
            container.Register(() => MongoFactory.GetCollectionFromDatabase<WhiteList>(connectionString));
        }
    }
}
